#include "window.h"
#include <iostream>
#include "engine/core/event_handler.h"


namespace kanon
{
    // window callbacks
    void framebuffer_size_callback(GLFWwindow* window, int width, int height)
    {
        glViewport(0, 0, width, height);
    }

    void mouse_move_callback(GLFWwindow* window, double xpos, double ypos)
    {
        Event evt;
        evt.context.data.f32[0] = (float)xpos;
        evt.context.data.f32[1] = (float)ypos;
        evt.handled = false;
        evt.type = EventType::EVENT_MOUSE_MOVE;
        fire_event(evt, window);
    }

    void mouse_button_callback(GLFWwindow* window, int button, int action, int mods)
    {
        Event evt;
        double xpos, ypos;
        glfwGetCursorPos(window, &xpos, &ypos);
        evt.context.data.f32[0] = (float)xpos;
        evt.context.data.f32[1] = (float)ypos;
        evt.handled = false;
        if (button == GLFW_MOUSE_BUTTON_LEFT)
            evt.type = EventType::EVENT_MOUSE_LEFT_DOWN;
        else if (button == GLFW_MOUSE_BUTTON_RIGHT)
            evt.type = EventType::EVENT_MOUSE_RIGHT_DOWN;
        else
            evt.type = EventType::EVENT_MOUSE_MIDDLE_DOWN;
        fire_event(evt, window);
    }

    void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods)
    {
        Event evt;

        evt.context.data.u8[0] = key;
        evt.context.data.u8[1] = scancode; // usually ignore
        evt.context.data.u8[2] = action;   // presss/release/repeat
        evt.context.data.u8[3] = mods;     // control key
        evt.handled = false;
        if (action == GLFW_PRESS)
            evt.type = EventType::EVENT_KEY_PRESS;
        else if (action == GLFW_RELEASE)
            evt.type = EventType::EVENT_KEY_RELEASE;
        else if (action == GLFW_REPEAT)
            evt.type = EventType::EVENT_KEY_REPEAT;
        else
            evt.type = EventType::EVENT_NONE;

        fire_event(evt, window);
    }

    void char_callback(GLFWwindow* window, unsigned int codepoint)
    {

    }

    void processInput(GLFWwindow* window) 
    {
        if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
            glfwSetWindowShouldClose(window, true);
    }

    Window::Window(const WindowInfo& info)
    {
        this->window = nullptr;
        this->width = info.width;
        this->height = info.height;
        this->title = info.title;
    }

    Window::~Window()
    {
        glfwDestroyWindow(window);
        glfwTerminate();
    }

    bool Window::Initialize()
    {
        glfwSetErrorCallback([](int error, const char* description) {
            fprintf(stderr, "GLFW Error (%d): %s\n", error, description);
        });

        if (!glfwInit()) {
            std::cerr << "Failed to initialize GLFW" << std::endl;
            return false;
        }

        glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
        glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
        glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
        glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

        window = glfwCreateWindow(this->width, this->height, this->title.c_str(), NULL, NULL);
        if (!window) {
            std::cerr << "Failed to create GLFW window" << std::endl;
            glfwTerminate();
            return false;
        }
        glfwMakeContextCurrent(window);
        glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);
        // mouse
        glfwSetCursorPosCallback(window, mouse_move_callback);
        glfwSetMouseButtonCallback(window, mouse_button_callback);
        // keyboard
        glfwSetKeyCallback(window, key_callback);
        glfwSetCharCallback(window, char_callback);

        if (glewInit() != GLEW_OK)
        {
            return false;
        }

        frame_time = glfwGetTime();

        return true;
    }

    bool Window::WindowClosed()
    {
        if (glfwWindowShouldClose(window))
            return true;
        else
            return false;
    }

    bool Window::Update()
    {
        processInput(window);
        return true;
    }

    bool Window::SwapBuffers()
    {
        glfwSwapBuffers(window);
        glfwPollEvents();
        return true;
    }

    double Window::GetFrameTime()
    {
        frame_time = glfwGetTime();
        return frame_time;
    }
}
